/*
 * irq.c
 *
 *  Created on: 01/09/2010
 *      Author: Wendell
 *
 * ===== IRQs e PICs======
 *
 * 		Requisições de Interrupção ou IRQs são interrupções chamadas pelos
 * dispositivos de hardware. Alguns dispositivos geram um IRQ quando eles têm
 * dados prontos para serem lidos, ou quando eles terminaram um comando de
 * escrever um buffer, por exemplo.
 * 		Esta é a chave para dizer que um dispositivo vai gerar um IRQ sempre que
 * este deseja chamar a atenção do processador. IRQs são gerados por tudo, desde
 * placas de rede e placas de som até seu mouse, teclado e portas seriais.
 *
 * 		Qualquer computador Compatível IBM PC/AT (algo com um 286 ou maior)
 * possui 2 chips que são usados para controlar IRQs. Estes 2 chips são
 * conhecidos como o Controle de Interrupções Programável (Programmable
 * Interrupt Controllers ou PICs). Estes PICs também com nome '8259'.
 * 		Um 8259 é como o controlador IRQ 'Master', e um é o controlador IRQ
 * 'Slave'. O slave é conectado pelo IRQ 2 no controlador master. O controlador
 * IRQ master é conectado diretamente para o processador em si, para enviar
 * sinais. Cada PIC pode tratar 8 IRQs. O PIC master trata dos IRQs 0 até 7, e o
 * PIC slave trata dos IRQs 8 até 15.
 * 		Lembre-se que o controlador slave é conectado para o controle primário
 * pelo IRQ 2: A _todo o tempo que um IRQ de 8 à 15 ocorre, IRQ2 dispara ao
 * mesmo tempo.
 *
 * 		Quando um dispositivo sinaliza um IRQ, lembre-se que uma interrupção é
 * gerada, e o CPU pausa qualquer coisa para chamar a ISR para tratar o IRQ
 * correspondente. O CPU então desempenha a ação necessária (quando for ler do
 * teclado, por exemplo), e então este precisa contar ao PIC que a interrupção
 * que veio da CPU acabou de executar a rotina correta.
 * 		O CPU conta ao PIC direito que a interrupção está completa escrevendo o
 * byte de comando 0x20 em hexadecimal para aquele PIC. O registro de comando no
 * PIC master existe na porta E/S 0x20, enquanto que o registrador do PIC slave
 * existe na porta E/S 0xA0.
 *
 * 		Antes de nós escrevermos nosso código de administração de IRQ, nós
 * devemos também saber que IRQ0 até IRQ7 são originalmente mapeadas  pela nas
 * entradas IDT 8 até 15. IRQ8 até IRQ15 são mapeadas pelas entradas IDT 0x70
 * até 0x78.
 * 		Se você se lembra da seção passada deste tutorial, entradas IDT 0 através
 * de 31 são reservadas para exceções. Felizmente, o Controle de Interrupções é
 * 'programável': Você pode mudar para que entradas IDT que seus IRQs são
 * mapeados. Para este tutorial, nós vamos mapear o IRQ0 até IRQ15 para as
 * entradas IDT 32 até 47.
 */

#include <io.h>

/**
 * Estes sao nossos proprios ISRs que aponam para nosso tratador IRQ especial
 * em vez de apontar para a função 'fault_handler' padrão
 */
extern void irq0();
extern void irq1();
extern void irq2();
extern void irq3();
extern void irq4();
extern void irq5();
extern void irq6();
extern void irq7();
extern void irq8();
extern void irq9();
extern void irq10();
extern void irq11();
extern void irq12();
extern void irq13();
extern void irq14();
extern void irq15();

/**
 * Este eh um array de ponteiros para funções. Usamos ele para
 * tratar nossos IRQ hnadlers personalizados para um dado IRQ
 */
void *irq_routines[256] = {
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0
};

/**
 * Instala um tratador IRQ personalizado para um dado IRQ
 */
void irqInstallHandler(int irq, void (*handler)(struct regs *r)) {
    irq_routines[irq] = handler;
}

/**
 * Limpa o tratador para um dado IRQ
 */
void irqUninstallHandler(int irq) {
    irq_routines[irq] = 0;
}
/**
 * Normalmente, IRQs 0 até 7 são mapeados para as entradas 8 até 15.
 * é um problema no modo protegido, porque a entrada IDT 8 é uma
*  Double Fault! Sem remapeamento, a _todo tempo que o IRQ0 dispara,
*  você obterá uma Exceção Double Fault, que NÃO é realmente
*  o que queremos...
*  Nós enviamos para o Controle Programável de
*  Interrupções (PICs - também chamado de 8259's) o comando
*  para fazer IRQ0 até 15 ser remapeado para as entradas IDT 32 até a 47.
 */
void irqRemap(void) {
    outportb(0x20, 0x11);
    outportb(0xA0, 0x11);
    outportb(0x21, 0x20);
    outportb(0xA1, 0x28);
    outportb(0x21, 0x04);
    outportb(0xA1, 0x02);
    outportb(0x21, 0x01);
    outportb(0xA1, 0x01);
    outportb(0x21, 0x0);
    outportb(0xA1, 0x0);
}

/**
 * Primeiros remapeamos os controladores de interrupções, e então instalamos
 * os ISRs apropriados para as entradas corretas do IDT. Isto é parecido com a
 * instalação dos tratamentos de exceções
 */
void irqInstall() {
    irqRemap();

    idtSetGate(32, (unsigned)irq0, 0x08, 0x8E);
    idtSetGate(33, (unsigned)irq1, 0x08, 0x8E);
    idtSetGate(34, (unsigned)irq2, 0x08, 0x8E);
    idtSetGate(35, (unsigned)irq3, 0x08, 0x8E);
    idtSetGate(36, (unsigned)irq4, 0x08, 0x8E);
    idtSetGate(37, (unsigned)irq5, 0x08, 0x8E);
    idtSetGate(38, (unsigned)irq6, 0x08, 0x8E);
    idtSetGate(39, (unsigned)irq7, 0x08, 0x8E);

    idtSetGate(40, (unsigned)irq8, 0x08, 0x8E);
    idtSetGate(41, (unsigned)irq9, 0x08, 0x8E);
    idtSetGate(42, (unsigned)irq10, 0x08, 0x8E);
    idtSetGate(43, (unsigned)irq11, 0x08, 0x8E);
    idtSetGate(44, (unsigned)irq12, 0x08, 0x8E);
    idtSetGate(45, (unsigned)irq13, 0x08, 0x8E);
    idtSetGate(46, (unsigned)irq14, 0x08, 0x8E);
    idtSetGate(47, (unsigned)irq15, 0x08, 0x8E);
}

/**
 *  Cada dos ISRs de IRQ apontam para esta função, em vez do
 *  'fault_handler' em 'isrs.c'. O Controlador de IRQ necessita
 *  ser avisado quando você está pronto para servi-lo então, assim
 *  você precisa enviar quando um comando "Fim da Interrupção" (0x20).
 *  Há dois chips 8259: O primeiro existe em 0x20, o segundo
 *  existe em 0xA0. Se o segundo controlador (um IRQ de 8 até
 *  15) obtêm uma interrupção, você precisa reconhecer a
 *  interrupção de AMBOS controladores, de outro modo, você somente envia
 *  um comando FDI (End of Interrupt (EOI)) para o primeiro controlador.
 *  Se você não enviar um FDI, você não obterá mais IRQs
 */
void irq_handler(struct regs *r) {
	//Este é um ponteiro de função em branco, vazio
    void (*handler)(struct regs *r);

    /**
     * Descobre se temos um handler 'customizado' para rodar para este IRQ,
     * e então, finalmente, roda ele.
     */
    handler = irq_routines[r->int_no - 32];
    if (handler) {
        handler(r);
    }

    /**
     * Se a entrada IDT que foi invocada é maior que 40
     * (significando IRQ8 - 15), então precisamos enviar um EOI para o
     * slave controller
     */
    if (r->int_no >= 40) {
        outportb(0xA0, 0x20);
    }

    /**
     * Em ambos os casos, precisamos enviar um EOI para o
     * master interrupt controller também
     */
    outportb(0x20, 0x20);
}
